﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using HardMock;
using HardMock.Recording;
using Xunit;

namespace AcceptanceTests
{
    #region LegacyCode To Test
    [Serializable]
    public class Person
    {
        public string Name { get; set; }
        public string FirstName { get; set; }
    }

    public interface IPersonDal
    {
        IEnumerable<Person> LoadAllPerson();

        //Often in Legacy code missing Liskov principle...
        IEnumerable<Person> LoadMissingPerson();
    }

    public class LegacyCode
    {
        private readonly IPersonDal personDal;

        public LegacyCode(IPersonDal personDal)
        {
            this.personDal = personDal;
        }

        public bool Check()
        {
            try
            {
                CheckPersons();
                return true;
            }
            catch (ArgumentException)
            {
                return false;
            }
        }

        private void CheckPersons()
        {
            var persons = personDal.LoadAllPerson().ToList();

            if (persons.Any(x => string.IsNullOrEmpty(x.Name)))
            {
                throw new ArgumentException();
            }

            if (personDal.LoadMissingPerson().Any())
            {
                throw new ArgumentException();
            }
        }
    }

    //Imagine that this is our pain point
    public class SimplePersonDal : IPersonDal
    {
        public IEnumerable<Person> LoadAllPerson()
        {
            return new List<Person> { new Person { FirstName = "toto" } };
        }

        public IEnumerable<Person> LoadMissingPerson()
        {
            return new List<Person>();
        }
    }
    #endregion

    /// <summary>
    /// TDD is dead : only for legacy code that doesn't have HardMock!
    /// 
    /// TDD is a good method in order to write right good on new project 
    /// or on existing project with few line of code.
    /// 
    /// Imagine a legacy project without test or manual test that you have to add feature
    /// using TDD. It is just impossible!
    /// 
    /// Why?
    /// Because TDD impose that you write first to failed test that prove you have to write missing
    /// Implementation.
    /// 
    /// On Legacy code, you can spent 1 week to write (mock with any mock framework) a test because you have to mock a lot of dependencies.
    /// 
    /// It's very stupid for ROI purpose to spend 1 week for a test that you delete when the refactoring is done.
    /// I prefer write test during 1 hour and then refactor during 1 week.
    /// 
    /// Why don't you first write an integration test which call real services, real databases, ...
    /// 
    /// It's more easy to write an integration test instead of write unit test but...
    /// 
    /// What's append when you checkin this test? It fail often because the build account doesn't have persmissions, ...
    /// But when you refactor legacy code you checkin every day or hour or minutes.
    /// 
    /// Then refactoring during more than one days...
    /// 
    /// Hard mock can produce mock based on integration test.
    /// 
    /// First write your integration test in a Unit Test.
    /// Write your assertions
    /// Then extract interface for every dependencies of your target class.
    /// Add the recorder behavior for every dependencies using AsRecord extension method.
    /// In the end of test write your recorded mock to a file using Snapshot extension method.
    /// 
    /// Now you have a mock stored into a file and you can replay using BuildMockFrom extension method.
    /// Now you can checkin your test and refactor your code.
    /// 
    /// </summary>
    public class Demo
    {
        [Fact]
        public void Record_mock_from_legacy_code()
        {
            var dependencyToRecord = new SimplePersonDal();
            var recorder = dependencyToRecord.AsRecorder<IPersonDal>();

            RunTest(recorder);

            var tempFileName = Path.GetTempFileName();
            using (var file = File.OpenWrite(tempFileName))
            {
                recorder.Snapshot(file);
                Console.WriteLine(tempFileName);
            }
        }

        private static void RunTest(IPersonDal dependencyToInject)
        {
            var legacyCode = new LegacyCode(dependencyToInject);

            Assert.False(legacyCode.Check());
        }

        [Fact]
        public void Test_legacy_code_by_injecting_previous_recorded_mock()
        {
            //Note that I have previously run the previous test to record mock
            //This test is same than previous but the unit test is build in 2 step
            //This test does not call SimplePersonDal Implementation dependency
            using (var data = GetMockData())
            {
                RunTest(data.BuildMockFrom<IPersonDal>());
            }
        }

        private static Stream GetMockData()
        {
            return typeof(Demo).Assembly.GetManifestResourceStream("AcceptanceTests.SimplePersonMockData.bin");
        }

        [Fact]
        public void Should_throw_not_record_exception_when_call_is_not_in_repository()
        {
            var recorder = new MUODate().AsRecorder<_MUODate>();

            recorder.Do("a", "b");

            using (var stream = new MemoryStream())
            {
                recorder.Snapshot(stream);

                var replayer = stream.BuildMockFrom<_MUODate>();

                Assert.Throws<NotRecordedException>(() => replayer.Do("b", "a"));
            }
        }

        [Fact]
        public void Record_then_replay_multiple_times()
        {
            var recorder = new MUODate().AsRecorder<_MUODate>();

            var target = new Target(recorder);

            const int times = 10;
            const string expected = "a";

            for (var i = 0; i < times; i++)
            {
                Assert.Equal(expected, target.JustDoIt());
            }

            using (var stream = new MemoryStream())
            {
                recorder.Snapshot(stream);

                var replayer = stream.BuildMockFrom<_MUODate>();

                var target2 = new Target(replayer);

                for (var i = 0; i < times; i++)
                {
                    Assert.Equal(expected, target2.JustDoIt());
                }
            }
        }
    }
}
